home *** CD-ROM | disk | FTP | other *** search
/ PC World 2007 September / PCWorld_2007-09_cd.bin / komunikace / firefox / Firefox Setup 2.0.0.6.exe / nonlocalized / components / nsMicrosummaryService.js < prev    next >
Encoding:
Text File  |  2007-07-25  |  79.7 KB  |  2,345 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Microsummarizer.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Myk Melez <myk@mozilla.org> (Original Author)
  22.  *  Simon B├╝nzli <zeniko@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. const Cc = Components.classes;
  39. const Ci = Components.interfaces;
  40.  
  41. const PERMS_FILE    = 0644;
  42. const MODE_WRONLY   = 0x02;
  43. const MODE_TRUNCATE = 0x20;
  44.  
  45. const NS_ERROR_MODULE_DOM = 2152923136;
  46. const NS_ERROR_DOM_BAD_URI = NS_ERROR_MODULE_DOM + 1012;
  47.  
  48. // How often to check for microsummaries that need updating, in milliseconds.
  49. const CHECK_INTERVAL = 15 * 1000; // 15 seconds
  50.  
  51. const MICSUM_NS = new Namespace("http://www.mozilla.org/microsummaries/0.1");
  52. const XSLT_NS = new Namespace("http://www.w3.org/1999/XSL/Transform");
  53.  
  54. //@line 60 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  55. const NC_NS                   = "http://home.netscape.com/NC-rdf#";
  56. const RDF_NS                  = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  57. const FIELD_RDF_TYPE          = RDF_NS + "type";
  58. const VALUE_MICSUM_BOOKMARK   = NC_NS + "MicsumBookmark";
  59. const VALUE_NORMAL_BOOKMARK   = NC_NS + "Bookmark";
  60. const FIELD_MICSUM_GEN_URI    = NC_NS + "MicsumGenURI";
  61. const FIELD_MICSUM_EXPIRATION = NC_NS + "MicsumExpiration";
  62. const FIELD_GENERATED_TITLE   = NC_NS + "GeneratedTitle";
  63. const FIELD_CONTENT_TYPE      = NC_NS + "ContentType";
  64. const FIELD_BOOKMARK_URL      = NC_NS + "URL";
  65. //@line 71 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  66.  
  67. const MAX_SUMMARY_LENGTH = 4096;
  68.  
  69. function MicrosummaryService() {}
  70.  
  71. MicrosummaryService.prototype = {
  72.  
  73. //@line 97 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  74.   // RDF Service
  75.   __rdf: null,
  76.   get _rdf() {
  77.     if (!this.__rdf)
  78.       this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
  79.                    getService(Ci.nsIRDFService);
  80.     return this.__rdf;
  81.   },
  82.  
  83.   // Bookmarks Data Source
  84.   __bmds: null,
  85.   get _bmds() {
  86.     if (!this.__bmds)
  87.       this.__bmds = this._rdf.GetDataSource("rdf:bookmarks");
  88.     return this.__bmds;
  89.   },
  90.  
  91.   // Old Bookmarks Service
  92.   __bms: null,
  93.   get _bms() {
  94.     if (!this.__bms)
  95.       this.__bms = this._bmds.QueryInterface(Ci.nsIBookmarksService);
  96.     return this.__bms;
  97.   },
  98. //@line 122 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  99.  
  100.   // IO Service
  101.   __ios: null,
  102.   get _ios() {
  103.     if (!this.__ios)
  104.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  105.                    getService(Ci.nsIIOService);
  106.     return this.__ios;
  107.   },
  108.  
  109.   // Observer Service
  110.   __obs: null,
  111.   get _obs() {
  112.     if (!this.__obs)
  113.       this.__obs = Cc["@mozilla.org/observer-service;1"].
  114.                    getService(Ci.nsIObserverService);
  115.     return this.__obs;
  116.   },
  117.  
  118.   /**
  119.    * Make a URI from a spec.
  120.    * @param   spec
  121.    *          The string spec of the URI.
  122.    * @returns An nsIURI object.
  123.    */
  124.   _uri: function MSS__uri(spec) {
  125.     return this._ios.newURI(spec, null, null);
  126.   },
  127.  
  128. //@line 152 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  129.   /**
  130.    * Make an RDF resource from a URI spec.
  131.    * @param   uriSpec
  132.    *          The URI spec to convert into a resource.
  133.    * @returns An nsIRDFResource object.
  134.    */
  135.   _resource: function MSS__resource(uriSpec) {
  136.     return this._rdf.GetResource(uriSpec);
  137.   },
  138.  
  139.   /**
  140.    * Make an RDF literal from a string.
  141.    * @param   str
  142.    *          The string from which to construct the literal.
  143.    * @returns An nsIRDFLiteral object
  144.    */
  145.   _literal: function MSS__literal(str) {
  146.     return this._rdf.GetLiteral(str);
  147.   },
  148. //@line 172 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  149.  
  150.   // Directory Locator
  151.   __dirs: null,
  152.   get _dirs() {
  153.     if (!this.__dirs)
  154.       this.__dirs = Cc["@mozilla.org/file/directory_service;1"].
  155.                    getService(Ci.nsIProperties);
  156.     return this.__dirs;
  157.   },
  158.  
  159.   // The update interval as specified by the user (defaults to 30 minutes)
  160.   get _updateInterval() {
  161.     var updateInterval =
  162.       getPref("browser.microsummary.updateInterval", 30);
  163.     // the minimum update interval is 1 minute
  164.     return Math.max(updateInterval, 1) * 60 * 1000;
  165.   },
  166.  
  167.   // A cache of local microsummary generators.  This gets built on startup
  168.   // by the _cacheLocalGenerators() method.
  169.   _localGenerators: {},
  170.  
  171.   // The timer that periodically checks for microsummaries needing updating.
  172.   _timer: null,
  173.  
  174.   // Interfaces this component implements.
  175.   interfaces: [Ci.nsIMicrosummaryService, Ci.nsIObserver, Ci.nsISupports],
  176.  
  177.   // nsISupports
  178.  
  179.   QueryInterface: function MSS_QueryInterface(iid) {
  180.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  181.     if (!iid.equals(Ci.nsIMicrosummaryService) &&
  182.         !iid.equals(Ci.nsIObserver) &&
  183.         !iid.equals(Ci.nsISupportsWeakReference) &&
  184.         !iid.equals(Ci.nsISupports))
  185.       throw Components.results.NS_ERROR_NO_INTERFACE;
  186.     return this;
  187.   },
  188.  
  189.   // nsIObserver
  190.  
  191.   observe: function MSS_observe(subject, topic, data) {
  192.     switch (topic) {
  193.       case "xpcom-shutdown":
  194.         this._destroy();
  195.         break;
  196.     }
  197.   },
  198.  
  199.   _init: function MSS__init() {
  200.     this._obs.addObserver(this, "xpcom-shutdown", true);
  201.  
  202.     // Periodically update microsummaries that need updating.
  203.     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  204.     var callback = {
  205.       _svc: this,
  206.       notify: function(timer) { this._svc._updateMicrosummaries() }
  207.     };
  208.     this._timer.initWithCallback(callback,
  209.                                  CHECK_INTERVAL,
  210.                                  this._timer.TYPE_REPEATING_SLACK);
  211.  
  212.     this._cacheLocalGenerators();
  213.   },
  214.   
  215.   _destroy: function MSS__destroy() {
  216.     this._timer.cancel();
  217.     this._timer = null;
  218.   },
  219.  
  220.   _updateMicrosummaries: function MSS__updateMicrosummaries() {
  221.     var bookmarks = this._getBookmarks();
  222.  
  223.     var now = Date.now();
  224.     var updateInterval = this._updateInterval;
  225.     for ( var i = 0; i < bookmarks.length; i++ ) {
  226.       var bookmarkID = bookmarks[i];
  227.  
  228.       // Skip this page if its microsummary hasn't expired yet.
  229.       if (this._hasField(bookmarkID, FIELD_MICSUM_EXPIRATION) &&
  230.           this._getField(bookmarkID, FIELD_MICSUM_EXPIRATION) > now)
  231.         continue;
  232.  
  233.       // Reset the expiration time immediately, so if the refresh is failing
  234.       // we don't try it every 15 seconds, potentially overloading the server.
  235.       this._setField(bookmarkID, FIELD_MICSUM_EXPIRATION, now + updateInterval);
  236.  
  237.       // Try to update the microsummary, but trap errors, so an update
  238.       // that throws doesn't prevent us from updating the rest of them.
  239.       try {
  240.         this.refreshMicrosummary(bookmarkID);
  241.       }
  242.       catch(ex) {
  243.         Components.utils.reportError(ex);
  244.       }
  245.     }
  246.   },
  247.   
  248.   _updateMicrosummary: function MSS__updateMicrosummary(bookmarkID, microsummary) {
  249.     this._setField(bookmarkID, FIELD_GENERATED_TITLE, microsummary.content);
  250.     this._setField(bookmarkID, FIELD_MICSUM_EXPIRATION,
  251.                    Date.now() + (microsummary.updateInterval || this._updateInterval));
  252.  
  253.     LOG("updated microsummary for page " + microsummary.pageURI.spec +
  254.         " to " + microsummary.content);
  255.   },
  256.  
  257.   /**
  258.    * Load local generators into the cache.
  259.    * 
  260.    */
  261.   _cacheLocalGenerators: function MSS__cacheLocalGenerators() {
  262.     // Load generators from the application directory.
  263.     var appDir = this._dirs.get("MicsumGens", Ci.nsIFile);
  264.     if (appDir.exists())
  265.       this._cacheLocalGeneratorDir(appDir);
  266.  
  267.     // Load generators from the user's profile.
  268.     var profileDir = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
  269.     if (profileDir.exists())
  270.       this._cacheLocalGeneratorDir(profileDir);
  271.   },
  272.  
  273.   /**
  274.    * Load local generators from a directory into the cache.
  275.    *
  276.    * @param   dir
  277.    *          nsIFile object pointing to directory containing generator files
  278.    * 
  279.    */
  280.   _cacheLocalGeneratorDir: function MSS__cacheLocalGeneratorDir(dir) {
  281.     var files = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  282.     var file = files.nextFile;
  283.  
  284.     while (file) {
  285.       // Recursively load generators so support packs containing
  286.       // lots of generators can organize them into multiple directories.
  287.       if (file.isDirectory())
  288.         this._cacheLocalGeneratorDir(file);
  289.       else
  290.         this._cacheLocalGeneratorFile(file);
  291.  
  292.       file = files.nextFile;
  293.     }
  294.     files.close();
  295.   },
  296.  
  297.   /**
  298.    * Load a local generator from a file into the cache.
  299.    * 
  300.    * @param   file
  301.    *          nsIFile object pointing to file from which to load generator
  302.    * 
  303.    */
  304.   _cacheLocalGeneratorFile: function MSS__cacheLocalGeneratorFile(file) {
  305.     var uri = this._ios.newFileURI(file);
  306.  
  307.     var t = this;
  308.     var callback =
  309.       function MSS_cacheLocalGeneratorCallback(resource) {
  310.         try     { t._handleLocalGenerator(resource) }
  311.         finally { resource.destroy() }
  312.       };
  313.  
  314.     var resource = new MicrosummaryResource(uri);
  315.     resource.load(callback);
  316.   },
  317.  
  318.   _handleLocalGenerator: function MSS__handleLocalGenerator(resource) {
  319.     if (!resource.isXML)
  320.       throw(resource.uri.spec + " microsummary generator loaded, but not XML");
  321.  
  322.     // Fix the generator's ID if it was installed before we started using URNs
  323.     // to uniquely identify generators.
  324.     // XXX This code can go away after Fx2 beta2, when enough users will have
  325.     // upgraded from earlier versions to make bug 346822 no longer significant.
  326.     this._fixGeneratorID(resource.content, resource.uri);
  327.  
  328.     var generator = new MicrosummaryGenerator();
  329.     generator.localURI = resource.uri;
  330.     generator.initFromXML(resource.content);
  331.  
  332.     // Add the generator to the local generators cache.
  333.     // XXX Figure out why Firefox crashes on shutdown if we index generators
  334.     // by uri.spec but doesn't crash if we index by uri.spec.split().join().
  335.     //this._localGenerators[generator.uri.spec] = generator;
  336.     this._localGenerators[generator.uri.spec.split().join()] = generator;
  337.  
  338.     LOG("loaded local microsummary generator\n" +
  339.         "  file: " + generator.localURI.spec + "\n" +
  340.         "    ID: " + generator.uri.spec);
  341.   },
  342.  
  343.   /**
  344.    * Fix the ID of a local generator that was installed before we started
  345.    * using URNs to uniquely identify local generators.
  346.    *
  347.    * @param   xmlDefinition
  348.    *          an nsIDOMDocument XML document defining the generator
  349.    * @param   localURI
  350.    *          an nsIURI file URI to the generator's local file
  351.    * 
  352.    */
  353.   _fixGeneratorID: function MSS__fixGeneratorID(xmlDefinition, localURI) {
  354.     var generatorNode = xmlDefinition.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
  355.  
  356.     if (!generatorNode)
  357.       return;
  358.  
  359.     // Don't fix generators that have already been fixed or were installed
  360.     // after we switched to identifying generators by URN.
  361.     if (generatorNode.hasAttribute("uri"))
  362.       return;
  363.  
  364.     // Don't fix generators that don't have any ID at all (we fall back to
  365.     // the local URI in these cases, which is useful for developers during
  366.     // the process of developing generators).
  367.     if (!generatorNode.hasAttribute("sourceURI"))
  368.       return;
  369.  
  370.     var oldURI = generatorNode.getAttribute("sourceURI");
  371.     var newURI = "urn:source:" + oldURI;
  372.  
  373.     LOG("fixing generator with old-style ID\n" +
  374.         "  old ID: " + oldURI + "\n" +
  375.         "  new ID: " + newURI);
  376.  
  377.     // Update the XML definition to reflect the change.
  378.     generatorNode.setAttribute("uri", newURI);
  379.  
  380.     // Save the updated XML definition to the local file.
  381.     var file = localURI.QueryInterface(Ci.nsIFileURL).file.clone();
  382.     this._saveGeneratorXML(xmlDefinition, file);
  383.  
  384.     // Update bookmarks in the bookmarks datastore that are using
  385.     // this microsummary generator to reflect the change.
  386.     this._changeField(FIELD_MICSUM_GEN_URI, oldURI, newURI);
  387.   },
  388.  
  389.   // nsIMicrosummaryService
  390.  
  391.   /**
  392.    * Install the microsummary generator from the resource at the supplied URI.
  393.    * Callable by content via the addMicrosummaryGenerator() sidebar method.
  394.    *
  395.    * @param   generatorURI
  396.    *          the URI of the resource providing the generator
  397.    *
  398.    */
  399.   addGenerator: function MSS_addGenerator(generatorURI) {
  400.     var t = this;
  401.     var callback =
  402.       function MSS_addGeneratorCallback(resource) {
  403.         try     { t._handleNewGenerator(resource) }
  404.         finally { resource.destroy() }
  405.       };
  406.  
  407.     var resource = new MicrosummaryResource(generatorURI);
  408.     resource.load(callback);
  409.   },
  410.  
  411.   _handleNewGenerator: function MSS__handleNewGenerator(resource) {
  412.     if (!resource.isXML)
  413.       throw(resource.uri.spec + " microsummary generator loaded, but not XML");
  414.  
  415.     // XXX Make sure it's a valid microsummary generator.
  416.  
  417.     var rootNode = resource.content.documentElement;
  418.  
  419.     // Add a reference to the URI from which we got this generator so we have
  420.     // a unique identifier for the generator and also so we can check back later
  421.     // for updates.
  422.     rootNode.setAttribute("uri", "urn:source:" + resource.uri.spec);
  423.  
  424.     this.installGenerator(resource.content);
  425.   },
  426.  
  427.   /**
  428.    * Install a microsummary generator from the given XML definition.
  429.    *
  430.    * @param   xmlDefinition
  431.    *          an nsIDOMDocument XML document defining the generator
  432.    *
  433.    * @returns the newly-installed nsIMicrosummaryGenerator generator
  434.    *
  435.    */
  436.   installGenerator: function MSS_installGenerator(xmlDefinition) {
  437.     var rootNode = xmlDefinition.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
  438.  
  439.     var generatorID = rootNode.getAttribute("uri");
  440.  
  441.     // The existing cache entry for this generator, if it is already installed.
  442.     var generator = this._localGenerators[generatorID];
  443.  
  444.     var file;
  445.     if (generator) {
  446.       // This generator is already installed.  Save it in the existing file
  447.       // (i.e. update the existing generator with the newly downloaded XML).
  448.       file = generator.localURI.QueryInterface(Ci.nsIFileURL).file.clone();
  449.     }
  450.     else {
  451.       // This generator is not already installed.  Save it as a new file.
  452.       var generatorName = rootNode.getAttribute("name");
  453.       var fileName = sanitizeName(generatorName) + ".xml";
  454.       file = this._dirs.get("UsrMicsumGens", Ci.nsIFile);
  455.       file.append(fileName);
  456.       file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  457.       generator = new MicrosummaryGenerator();
  458.       generator.localURI = this._ios.newFileURI(file);
  459.       this._localGenerators[generatorID] = generator;
  460.     }
  461.  
  462.     // Initialize (or reinitialize) the generator from its XML definition,
  463.     // the save the definition to the generator's file.
  464.     generator.initFromXML(xmlDefinition);
  465.     this._saveGeneratorXML(xmlDefinition, file);
  466.  
  467.     LOG("installed generator " + generatorID);
  468.  
  469.     return generator;
  470.   },
  471.  
  472.   /**
  473.    * Save a generator's XML definition to a local file.
  474.    *
  475.    * @param   xmlDefinition
  476.    *          an nsIDOMDocument XML document defining the generator
  477.    * @param   file
  478.    *          an nsIFile file representing the generator's local file
  479.    * 
  480.    */
  481.   _saveGeneratorXML: function MSS_saveGeneratorXML(xmlDefinition, file) {
  482.     LOG("saving definition to " + file.path);
  483.  
  484.     // Write the generator XML to the local file.
  485.     var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  486.                        createInstance(Ci.nsIFileOutputStream);
  487.     var localFile = file.QueryInterface(Ci.nsILocalFile);
  488.     outputStream.init(localFile, (MODE_WRONLY | MODE_TRUNCATE), PERMS_FILE, 0);
  489.     var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
  490.                      createInstance(Ci.nsIDOMSerializer);
  491.     serializer.serializeToStream(xmlDefinition, outputStream, null);
  492.     if (outputStream instanceof Ci.nsISafeOutputStream) {
  493.       try       { outputStream.finish() }
  494.       catch (e) { outputStream.close()  }
  495.     }
  496.     else
  497.       outputStream.close();
  498.   },
  499.  
  500.   /**
  501.    * Get the set of microsummaries available for a given page.  The set
  502.    * might change after this method returns, since this method will trigger
  503.    * an asynchronous load of the page in question (if it isn't already loaded)
  504.    * to see if it references any page-specific microsummaries.
  505.    *
  506.    * If the caller passes a bookmark ID, and one of the microsummaries
  507.    * is the current one for the bookmark, this method will retrieve content
  508.    * from the datastore for that microsummary, which is useful when callers
  509.    * want to display a list of microsummaries for a page that isn't loaded,
  510.    * and they want to display the actual content of the selected microsummary
  511.    * immediately (rather than after the content is asynchronously loaded).
  512.    *
  513.    * @param   pageURI
  514.    *          the URI of the page for which to retrieve available microsummaries
  515.    *
  516.    * @param   bookmarkID (optional)
  517.    *          the ID of the bookmark for which this method is being called
  518.    *
  519.    * @returns an nsIMicrosummarySet of nsIMicrosummaries for the given page
  520.    *
  521.    */
  522.   getMicrosummaries: function MSS_getMicrosummaries(pageURI, bookmarkID) {
  523.     var microsummaries = new MicrosummarySet();
  524.  
  525.     // Get microsummaries defined by local generators.
  526.     for (var genURISpec in this._localGenerators) {
  527.       var generator = this._localGenerators[genURISpec];
  528.  
  529.       if (generator.appliesToURI(pageURI)) {
  530.         var microsummary = new Microsummary(pageURI, generator);
  531.  
  532.         // If this is the current microsummary for this bookmark, load the content
  533.         // from the datastore so it shows up immediately in microsummary picking UI.
  534.         if (bookmarkID && this.isMicrosummary(bookmarkID, microsummary))
  535.           microsummary.content = this._getField(bookmarkID, FIELD_GENERATED_TITLE);
  536.  
  537.         microsummaries.AppendElement(microsummary);
  538.       }
  539.     }
  540.  
  541.     // Get microsummaries defined by the page.  If we don't have the page,
  542.     // download it asynchronously, and then finish populating the set.
  543.     var resource = getLoadedMicrosummaryResource(pageURI);
  544.     if (resource) {
  545.       try     { microsummaries.extractFromPage(resource) }
  546.       finally { resource.destroy() }
  547.     }
  548.     else {
  549.       // Load the page with a callback that will add the page's microsummaries
  550.       // to the set once the page has loaded.
  551.       var callback = function MSS_extractFromPageCallback(resource) {
  552.         try     { microsummaries.extractFromPage(resource) }
  553.         finally { resource.destroy() }
  554.       };
  555.  
  556.       try {
  557.         resource = new MicrosummaryResource(pageURI);
  558.         resource.load(callback);
  559.       }
  560.       catch(e) {
  561.         // We don't have to do anything special if the call fails besides
  562.         // destroying the Resource object.  We can just return the list
  563.         // of microsummaries without including page-defined microsummaries.
  564.         if (resource)
  565.           resource.destroy();
  566.         LOG("error downloading page to extract its microsummaries: " + e);
  567.       }
  568.     }
  569.  
  570.     return microsummaries;
  571.   },
  572.  
  573.   /**
  574.    * Change all occurrences of a specific value in a given field to a new value.
  575.    *
  576.    * @param   fieldName
  577.    *          the name of the field whose values should be changed
  578.    * @param   oldValue
  579.    *          the value that should be changed
  580.    * @param   newValue
  581.    *          the value to which it should be changed
  582.    *
  583.    */
  584.   _changeField: function MSS__changeField(fieldName, oldValue, newValue) {
  585.     var bookmarks = this._getBookmarks();
  586.  
  587.     for ( var i = 0; i < bookmarks.length; i++ ) {
  588.       var bookmarkID = bookmarks[i];
  589.  
  590.       if (this._hasField(bookmarkID, fieldName) &&
  591.           this._getField(bookmarkID, fieldName) == oldValue)
  592.         this._setField(bookmarkID, fieldName, newValue);
  593.     }
  594.   },
  595.  
  596. //@line 705 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  597.   /**
  598.    * Get the set of bookmarks with microsummaries.
  599.    *
  600.    * This is the internal version of this method, which is not accessible
  601.    * via XPCOM but is more performant; inside this component, use this version.
  602.    * Outside the component, use getBookmarks (no underscore prefix) instead.
  603.    *
  604.    * @returns an array of bookmark IDs
  605.    *
  606.    */
  607.   _getBookmarks: function MSS__getBookmarks() {
  608.     var bookmarks = [];
  609.  
  610.     var resources = this._bmds.GetSources(this._resource(FIELD_RDF_TYPE),
  611.                                           this._resource(VALUE_MICSUM_BOOKMARK),
  612.                                           true);
  613.     while (resources.hasMoreElements()) {
  614.       var resource = resources.getNext().QueryInterface(Ci.nsIRDFResource);
  615.  
  616.       // When a bookmark gets deleted or cut, most of its arcs get removed
  617.       // from the data source, but a few of them remain, in particular its RDF
  618.       // type arc.  So just because this resource has a MicsumBookmark type,
  619.       // that doesn't mean it's a real bookmark!  We need to check.
  620.       if (!this._bms.isBookmarkedResource(resource))
  621.         continue;
  622.  
  623.       bookmarks.push(resource);
  624.     }
  625.  
  626.     return bookmarks;
  627.   },
  628.  
  629.   _getField: function MSS__getField(bookmarkID, fieldName) {
  630.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  631.     var fieldValue;
  632.  
  633.     var node = this._bmds.GetTarget(bookmarkResource,
  634.                                     this._resource(fieldName),
  635.                                     true);
  636.     if (node) {
  637.       if (fieldName == FIELD_RDF_TYPE)
  638.         fieldValue = node.QueryInterface(Ci.nsIRDFResource).Value;
  639.       else
  640.         fieldValue = node.QueryInterface(Ci.nsIRDFLiteral).Value;
  641.     }
  642.     else
  643.       fieldValue = null;
  644.  
  645.     return fieldValue;
  646.   },
  647.  
  648.   _setField: function MSS__setField(bookmarkID, fieldName, fieldValue) {
  649.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  650.  
  651.     if (this._hasField(bookmarkID, fieldName)) {
  652.       var oldValue = this._getField(bookmarkID, fieldName);
  653.       this._bmds.Change(bookmarkResource,
  654.                         this._resource(fieldName),
  655.                         this._literal(oldValue),
  656.                         this._literal(fieldValue));
  657.     }
  658.     else {
  659.       this._bmds.Assert(bookmarkResource,
  660.                         this._resource(fieldName),
  661.                         this._literal(fieldValue),
  662.                         true);
  663.     }
  664.   },
  665.  
  666.   _clearField: function MSS__clearField(bookmarkID, fieldName) {
  667.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  668.  
  669.     var node = this._bmds.GetTarget(bookmarkResource,
  670.                                     this._resource(fieldName),
  671.                                     true);
  672.     if (node) {
  673.       this._bmds.Unassert(bookmarkResource,
  674.                           this._resource(fieldName),
  675.                           node);
  676.     }
  677.   },
  678.  
  679.   _hasField: function MSS__hasField(bookmarkID, fieldName) {
  680.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  681.  
  682.     var node = this._bmds.GetTarget(bookmarkResource,
  683.                                     this._resource(fieldName),
  684.                                     true);
  685.     return node ? true : false;
  686.   },
  687.  
  688.   /**
  689.    * Get the URI of the page to which a given bookmark refers.
  690.    *
  691.    * @param   bookmarkResource
  692.    *          an nsIResource uniquely identifying the bookmark
  693.    *
  694.    * @returns an nsIURI object representing the bookmark's page,
  695.    *          or null if the bookmark doesn't exist
  696.    *
  697.    */
  698.   _getPageForBookmark: function MSS__getPageForBookmark(bookmarkID) {
  699.     var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  700.  
  701.     var node = this._bmds.GetTarget(bookmarkResource,
  702.                                     this._resource(NC_NS + "URL"),
  703.                                     true);
  704.  
  705.     if (!node)
  706.       return null;
  707.  
  708.     var pageSpec = node.QueryInterface(Ci.nsIRDFLiteral).Value;
  709.     var pageURI = this._uri(pageSpec);
  710.     return pageURI;
  711.   },
  712. //@line 821 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  713.  
  714.   /**
  715.    * Get the set of bookmarks with microsummaries.
  716.    *
  717.    * Bookmark IDs are nsIRDFResource objects on builds with old RDF-based
  718.    * bookmarks and nsIURI objects on builds with new Places-based bookmarks.
  719.    *
  720.    * This is the external version of this method and is accessible via XPCOM.
  721.    * Use it outside this component. Inside the component, use _getBookmarks
  722.    * (with underscore prefix) instead for performance.
  723.    *
  724.    * @returns an nsISimpleEnumerator enumeration of bookmark IDs
  725.    *
  726.    */
  727.   getBookmarks: function MSS_getBookmarks() {
  728.     return new ArrayEnumerator(this._getBookmarks());
  729.   },
  730.  
  731.   /**
  732.    * Get the current microsummary for the given bookmark.
  733.    *
  734.    * @param   bookmarkID
  735.    *          the bookmark for which to get the current microsummary
  736.    *
  737.    * @returns the current microsummary for the bookmark, or null
  738.    *          if the bookmark does not have a current microsummary
  739.    *
  740.    */
  741.   getMicrosummary: function MSS_getMicrosummary(bookmarkID) {
  742.     if (!this.hasMicrosummary(bookmarkID))
  743.       return null;
  744.  
  745.     var pageURI = this._getPageForBookmark(bookmarkID);
  746.     var generatorURI = this._uri(this._getField(bookmarkID, FIELD_MICSUM_GEN_URI));
  747.     
  748.     var localGenerator = this._localGenerators[generatorURI.spec];
  749.  
  750.     var microsummary = new Microsummary(pageURI, localGenerator);
  751.     if (!localGenerator)
  752.       microsummary.generator.uri = generatorURI;
  753.  
  754.     return microsummary;
  755.   },
  756.  
  757.   /**
  758.    * Set the current microsummary for the given bookmark.
  759.    *
  760.    * @param   bookmarkID
  761.    *          the bookmark for which to set the current microsummary
  762.    *
  763.    * @param   microsummary
  764.    *          the microsummary to set as the current one
  765.    *
  766.    */
  767.   setMicrosummary: function MSS_setMicrosummary(bookmarkID, microsummary) {
  768. //@line 877 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  769.     // Make sure that the bookmark is of type MicsumBookmark
  770.     // because that's what the template rules are matching
  771.     if (this._getField(bookmarkID, FIELD_RDF_TYPE) != VALUE_MICSUM_BOOKMARK) {
  772.       // Force the bookmark trees to rebuild, since they don't seem
  773.       // to be rebuilding on their own (bug 348928).
  774.       this._bmds.beginUpdateBatch();
  775.  
  776.       var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  777.       if (this._hasField(bookmarkID, FIELD_RDF_TYPE)) {
  778.         var oldValue = this._getField(bookmarkID, FIELD_RDF_TYPE);
  779.         this._bmds.Change(bookmarkResource,
  780.                           this._resource(FIELD_RDF_TYPE),
  781.                           this._resource(oldValue),
  782.                           this._resource(VALUE_MICSUM_BOOKMARK));
  783.       }
  784.       else {
  785.         this._bmds.Assert(bookmarkResource,
  786.                           this._resource(FIELD_RDF_TYPE),
  787.                           this._resource(VALUE_MICSUM_BOOKMARK),
  788.                           true);
  789.       }
  790.  
  791.       this._bmds.endUpdateBatch();
  792.     }
  793. //@line 902 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  794.     this._setField(bookmarkID, FIELD_MICSUM_GEN_URI, microsummary.generator.uri.spec);
  795.  
  796.     if (microsummary.content) {
  797.       this._updateMicrosummary(bookmarkID, microsummary);
  798.     }
  799.     else {
  800.       // Display a static title initially (unless there's already one set)
  801.       if (!this._getField(bookmarkID, FIELD_GENERATED_TITLE))
  802.         this._setField(bookmarkID, FIELD_GENERATED_TITLE,
  803.                        microsummary.generator.name || microsummary.generator.uri.spec);
  804.  
  805.       this.refreshMicrosummary(bookmarkID);
  806.     }
  807.   },
  808.  
  809.   /**
  810.    * Remove the current microsummary for the given bookmark.
  811.    *
  812.    * @param   bookmarkID
  813.    *          the bookmark for which to remove the current microsummary
  814.    *
  815.    */
  816.   removeMicrosummary: function MSS_removeMicrosummary(bookmarkID) {
  817. //@line 926 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  818.     // Set the bookmark's RDF type back to the normal bookmark type
  819.     if (this._getField(bookmarkID, FIELD_RDF_TYPE) == VALUE_MICSUM_BOOKMARK) {
  820.       // Force the bookmark trees to rebuild, since they don't seem
  821.       // to be rebuilding on their own (bug 348928).
  822.       this._bmds.beginUpdateBatch();
  823.  
  824.       var bookmarkResource = bookmarkID.QueryInterface(Ci.nsIRDFResource);
  825.       this._bmds.Change(bookmarkResource,
  826.                         this._resource(FIELD_RDF_TYPE),
  827.                         this._resource(VALUE_MICSUM_BOOKMARK),
  828.                         this._resource(VALUE_NORMAL_BOOKMARK));
  829.  
  830.       this._bmds.endUpdateBatch();
  831.     }
  832. //@line 941 "/cygdrive/c/builds/tinderbox/Fx-Mozilla1.8-release/WINNT_5.2_Depend/mozilla/browser/components/microsummaries/src/nsMicrosummaryService.js.in"
  833.  
  834.     var fields = [FIELD_MICSUM_GEN_URI,
  835.                   FIELD_MICSUM_EXPIRATION,
  836.                   FIELD_GENERATED_TITLE,
  837.                   FIELD_CONTENT_TYPE];
  838.  
  839.     for ( var i = 0; i < fields.length; i++ ) {
  840.       var field = fields[i];
  841.       if (this._hasField(bookmarkID, field))
  842.         this._clearField(bookmarkID, field);
  843.     }
  844.   },
  845.  
  846.   /**
  847.    * Whether or not the given bookmark has a current microsummary.
  848.    *
  849.    * @param   bookmarkID
  850.    *          the bookmark for which to set the current microsummary
  851.    *
  852.    * @returns a boolean representing whether or not the given bookmark
  853.    *          has a current microsummary
  854.    *
  855.    */
  856.   hasMicrosummary: function MSS_hasMicrosummary(bookmarkID) {
  857.     return this._hasField(bookmarkID, FIELD_MICSUM_GEN_URI);
  858.   },
  859.  
  860.   /**
  861.    * Whether or not the given microsummary is the current microsummary
  862.    * for the given bookmark.
  863.    *
  864.    * @param   bookmarkID
  865.    *          the bookmark to check
  866.    *
  867.    * @param   microsummary
  868.    *          the microsummary to check
  869.    *
  870.    * @returns whether or not the microsummary is the current one
  871.    *          for the bookmark
  872.    *
  873.    */
  874.   isMicrosummary: function MSS_isMicrosummary(bookmarkID, microsummary) {
  875.     if (!this.hasMicrosummary(bookmarkID))
  876.       return false;
  877.  
  878.     var currentGen = this._getField(bookmarkID, FIELD_MICSUM_GEN_URI);
  879.  
  880.     if (microsummary.generator.uri.equals(this._uri(currentGen)))
  881.       return true;
  882.  
  883.     return false
  884.   },
  885.  
  886.   /**
  887.    * Refresh a microsummary, updating its value in the datastore and UI.
  888.    * If this method can refresh the microsummary instantly, it will.
  889.    * Otherwise, it'll asynchronously download the necessary information
  890.    * (the generator and/or page) before refreshing the microsummary.
  891.    *
  892.    * Callers should check the "content" property of the returned microsummary
  893.    * object to distinguish between sync and async refreshes.  If its value
  894.    * is "null", then it's an async refresh, and the caller should register
  895.    * itself as an nsIMicrosummaryObserver via nsIMicrosummary.addObserver()
  896.    * to find out when the refresh completes.
  897.    *
  898.    * @param   bookmarkID
  899.    *          the bookmark whose microsummary is being refreshed
  900.    *
  901.    * @returns the microsummary being refreshed
  902.    *
  903.    */
  904.   refreshMicrosummary: function MSS_refreshMicrosummary(bookmarkID) {
  905.     if (!this.hasMicrosummary(bookmarkID))
  906.       throw "bookmark " + bookmarkID + " does not have a microsummary";
  907.  
  908.     var pageURI = this._getPageForBookmark(bookmarkID);
  909.     if (!pageURI)
  910.       throw("can't get URL for bookmark with ID " + bookmarkID);
  911.     var generatorURI = this._uri(this._getField(bookmarkID, FIELD_MICSUM_GEN_URI));
  912.  
  913.     var localGenerator = this._localGenerators[generatorURI.spec];
  914.  
  915.     var microsummary = new Microsummary(pageURI, localGenerator);
  916.     if (!localGenerator)
  917.       microsummary.generator.uri = generatorURI;
  918.  
  919.     // A microsummary observer that calls the microsummary service
  920.     // to update the datastore when the microsummary finishes loading.
  921.     var observer = {
  922.       _svc: this,
  923.       _bookmarkID: bookmarkID,
  924.       onContentLoaded: function MSS_observer_onContentLoaded(microsummary) {
  925.         try {
  926.           this._svc._updateMicrosummary(this._bookmarkID, microsummary);
  927.         }
  928.         finally {
  929.           this._svc = null;
  930.           this._bookmarkID = null;
  931.           microsummary.removeObserver(this);
  932.         }
  933.       }
  934.     };
  935.  
  936.     // Register the observer with the microsummary and trigger the microsummary
  937.     // to update itself.
  938.     microsummary.addObserver(observer);
  939.     microsummary.update();
  940.     
  941.     return microsummary;
  942.   }
  943. };
  944.  
  945.  
  946.  
  947.  
  948.  
  949. function Microsummary(pageURI, generator) {
  950.   this._observers = [];
  951.   this.pageURI = pageURI;
  952.   this.generator = generator ? generator : new MicrosummaryGenerator();
  953. }
  954.  
  955. Microsummary.prototype = {
  956.   // The microsummary service.
  957.   __mss: null,
  958.   get _mss() {
  959.     if (!this.__mss)
  960.       this.__mss = Cc["@mozilla.org/microsummary/service;1"].
  961.                    getService(Ci.nsIMicrosummaryService);
  962.     return this.__mss;
  963.   },
  964.  
  965.   // IO Service
  966.   __ios: null,
  967.   get _ios() {
  968.     if (!this.__ios)
  969.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  970.                    getService(Ci.nsIIOService);
  971.     return this.__ios;
  972.   },
  973.  
  974.   /**
  975.    * Make a URI from a spec.
  976.    * @param   spec
  977.    *          The string spec of the URI.
  978.    * @returns An nsIURI object.
  979.    */
  980.   _uri: function MSS__uri(spec) {
  981.     return this._ios.newURI(spec, null, null);
  982.   },
  983.  
  984.   interfaces: [Ci.nsIMicrosummary, Ci.nsISupports],
  985.  
  986.   // nsISupports
  987.  
  988.   QueryInterface: function (iid) {
  989.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  990.     if (!iid.equals(Ci.nsIMicrosummary) &&
  991.         !iid.equals(Ci.nsISupports))
  992.       throw Components.results.NS_ERROR_NO_INTERFACE;
  993.     return this;
  994.   },
  995.  
  996.   // nsIMicrosummary
  997.  
  998.   _content: null,
  999.   get content() {
  1000.     // If we have everything we need to generate the content, generate it.
  1001.     if (this._content == null &&
  1002.         this.generator.loaded &&
  1003.         (this.pageContent || !this.generator.needsPageContent)) {
  1004.       this._content = this.generator.generateMicrosummary(this.pageContent);
  1005.       this.updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
  1006.     }
  1007.  
  1008.     // Note: we return "null" if the content wasn't already generated and we
  1009.     // couldn't retrieve it from the generated title annotation or generate it
  1010.     // ourselves.  So callers shouldn't count on getting content; instead,
  1011.     // they should call update if the return value of this getter is "null",
  1012.     // setting an observer to tell them when content generation is done.
  1013.     return this._content;
  1014.   },
  1015.   set content(newValue) { this._content = newValue },
  1016.  
  1017.   _generator: null,
  1018.   get generator()            { return this._generator },
  1019.   set generator(newValue)    { this._generator = newValue },
  1020.  
  1021.   _pageURI: null,
  1022.   get pageURI()              { return this._pageURI },
  1023.   set pageURI(newValue)      { this._pageURI = newValue },
  1024.  
  1025.   _pageContent: null,
  1026.   get pageContent() {
  1027.     if (!this._pageContent) {
  1028.       // If the page is currently loaded into a browser window, use that.
  1029.       var resource = getLoadedMicrosummaryResource(this.pageURI);
  1030.       if (resource) {
  1031.         this._pageContent = resource.content;
  1032.         resource.destroy();
  1033.       }
  1034.     }
  1035.  
  1036.     return this._pageContent;
  1037.   },
  1038.   set pageContent(newValue) { this._pageContent = newValue },
  1039.  
  1040.   _updateInterval: null,
  1041.   get updateInterval()         { return this._updateInterval; },
  1042.   set updateInterval(newValue) { this._updateInterval = newValue; },
  1043.  
  1044.   // nsIMicrosummary
  1045.  
  1046.   _observers: null,
  1047.  
  1048.   addObserver: function MS_addObserver(observer) {
  1049.     // Register the observer, but only if it isn't already registered,
  1050.     // so that we don't call the same observer twice for any given change.
  1051.     if (this._observers.indexOf(observer) == -1)
  1052.       this._observers.push(observer);
  1053.   },
  1054.   
  1055.   removeObserver: function MS_removeObserver(observer) {
  1056.     //NS_ASSERT(this._observers.indexOf(observer) != -1,
  1057.     //          "can't remove microsummary observer " + observer + ": not registered");
  1058.   
  1059.     //this._observers =
  1060.     //  this._observers.filter(function(i) { observer != i });
  1061.     if (this._observers.indexOf(observer) != -1)
  1062.       this._observers.splice(this._observers.indexOf(observer), 1);
  1063.   },
  1064.  
  1065.   /**
  1066.    * Regenerates the microsummary, asynchronously downloading its generator
  1067.    * and content as needed.
  1068.    *
  1069.    */
  1070.   update: function MS_update() {
  1071.     LOG("microsummary.update called for page:\n  " + this.pageURI.spec +
  1072.         "\nwith generator:\n  " + this.generator.uri.spec);
  1073.  
  1074.     var t = this;
  1075.  
  1076.     // If we don't have the generator, download it now.  After it downloads,
  1077.     // we'll re-call this method to continue updating the microsummary.
  1078.     if (!this.generator.loaded) {
  1079.       // If this generator is identified by a URN, then it's a local generator
  1080.       // that should have been cached on application start, so it's missing.
  1081.       if (this.generator.uri.scheme == "urn") {
  1082.         // If it was installed via nsSidebar::addMicrosummaryGenerator (i.e. it
  1083.         // has a URN that identifies the source URL from which we installed it),
  1084.         // try to reinstall it (once).
  1085.         if (/^source:/.test(this.generator.uri.path)) {
  1086.           this._reinstallMissingGenerator();
  1087.           return;
  1088.         }
  1089.         else
  1090.           throw "missing local generator: " + this.generator.uri.spec;
  1091.       }
  1092.  
  1093.       LOG("generator not yet loaded; downloading it");
  1094.       var generatorCallback =
  1095.         function MS_generatorCallback(resource) {
  1096.           try     { t._handleGeneratorLoad(resource) }
  1097.           finally { resource.destroy() }
  1098.         };
  1099.       var resource = new MicrosummaryResource(this.generator.uri);
  1100.       resource.load(generatorCallback);
  1101.       return;
  1102.     }
  1103.  
  1104.     // If we need the page content, and we don't have it, download it now.
  1105.     // Afterwards we'll re-call this method to continue updating the microsummary.
  1106.     if (this.generator.needsPageContent && !this.pageContent) {
  1107.       LOG("page content not yet loaded; downloading it");
  1108.       var pageCallback =
  1109.         function MS_pageCallback(resource) {
  1110.           try     { t._handlePageLoad(resource) }
  1111.           finally { resource.destroy() }
  1112.         };
  1113.       var resource = new MicrosummaryResource(this.pageURI);
  1114.       resource.load(pageCallback);
  1115.       return;
  1116.     }
  1117.  
  1118.     LOG("generator (and page, if needed) both loaded; generating microsummary");
  1119.  
  1120.     // Now that we have both the generator and (if needed) the page content,
  1121.     // generate the microsummary, then let the observers know about it.
  1122.     this.content = this.generator.generateMicrosummary(this.pageContent);
  1123.     this.updateInterval = this.generator.calculateUpdateInterval(this.pageContent);
  1124.     this.pageContent = null;
  1125.     for ( var i = 0; i < this._observers.length; i++ )
  1126.       this._observers[i].onContentLoaded(this);
  1127.  
  1128.     LOG("generated microsummary: " + this.content);
  1129.   },
  1130.  
  1131.   _handleGeneratorLoad: function MS__handleGeneratorLoad(resource) {
  1132.     LOG(this.generator.uri.spec + " microsummary generator downloaded");
  1133.     if (resource.isXML)
  1134.       this.generator.initFromXML(resource.content);
  1135.     else if (resource.contentType == "text/plain")
  1136.       this.generator.initFromText(resource.content);
  1137.     else if (resource.contentType == "text/html")
  1138.       this.generator.initFromText(resource.content.body.textContent);
  1139.     else
  1140.       throw("generator is neither XML nor plain text");
  1141.  
  1142.     // Only trigger a [content] update if we were able to init the generator. 
  1143.     if (this.generator.loaded)
  1144.       this.update();
  1145.   },
  1146.  
  1147.   _handlePageLoad: function MS__handlePageLoad(resource) {
  1148.     if (!resource.isXML && resource.contentType != "text/html")
  1149.       throw("page is neither HTML nor XML");
  1150.  
  1151.     this.pageContent = resource.content;
  1152.     this.update();
  1153.   },
  1154.  
  1155.   /**
  1156.    * Try to reinstall a missing local generator that was originally installed
  1157.    * from a URL using nsSidebar::addMicrosumaryGenerator.
  1158.    *
  1159.    */
  1160.   _reinstallMissingGenerator: function MS__reinstallMissingGenerator() {
  1161.     LOG("attempting to reinstall missing generator " + this.generator.uri.spec);
  1162.  
  1163.     var t = this;
  1164.  
  1165.     var loadCallback =
  1166.       function MS_missingGeneratorLoadCallback(resource) {
  1167.         try     { t._handleMissingGeneratorLoad(resource) }
  1168.         finally { resource.destroy() }
  1169.       };
  1170.  
  1171.     var errorCallback =
  1172.       function MS_missingGeneratorErrorCallback(resource) {
  1173.         try     { t._handleMissingGeneratorError(resource) }
  1174.         finally { resource.destroy() }
  1175.       };
  1176.  
  1177.     try {
  1178.       // Extract the URI from which the generator was originally installed.
  1179.       var sourceURL = this.generator.uri.path.replace(/^source:/, "");
  1180.       var sourceURI = this._uri(sourceURL);
  1181.  
  1182.       var resource = new MicrosummaryResource(sourceURI);
  1183.       resource.load(loadCallback, errorCallback);
  1184.     }
  1185.     catch(ex) {
  1186.       Components.utils.reportError(ex);
  1187.       this._handleMissingGeneratorError();
  1188.     }
  1189.   },
  1190.  
  1191.   /**
  1192.    * Handle a load event for a missing local generator by trying to reinstall
  1193.    * the generator.  If this fails, call _handleMissingGeneratorError to unset
  1194.    * microsummaries for bookmarks using this generator so we don't repeatedly
  1195.    * try to reinstall the generator, creating too much traffic to the website
  1196.    * from which we downloaded it.
  1197.    *
  1198.    * @param resource
  1199.    *        the nsIMicrosummaryResource representing the downloaded generator
  1200.    *
  1201.    */
  1202.   _handleMissingGeneratorLoad: function MS__handleMissingGeneratorLoad(resource) {
  1203.     try {
  1204.       // Make sure the generator is XML, since local generators have to be.
  1205.       if (!resource.isXML)
  1206.         throw("downloaded, but not XML " + this.generator.uri.spec);
  1207.  
  1208.       // Store the generator's ID in its XML definition.
  1209.       var generatorID = this.generator.uri.spec;
  1210.       resource.content.documentElement.setAttribute("uri", generatorID);
  1211.  
  1212.       // Reinstall the generator and replace our placeholder generator object
  1213.       // with the newly installed generator.
  1214.       this.generator = this._mss.installGenerator(resource.content);
  1215.  
  1216.       // A reinstalled generator should always be loaded.  But just in case
  1217.       // it isn't, throw an error so we don't get into an infinite loop
  1218.       // (otherwise this._update would try again to reinstall it).
  1219.       if (!this.generator.loaded)
  1220.         throw("supposedly installed, but not in cache " + this.generator.uri.spec);
  1221.     }
  1222.     catch(ex) {
  1223.       Components.utils.reportError(ex);
  1224.       this._handleMissingGeneratorError(resource);
  1225.       return;
  1226.     }
  1227.   
  1228.     LOG("reinstall succeeded; resuming update " + this.generator.uri.spec);
  1229.     this.update();
  1230.   },
  1231.  
  1232.   /**
  1233.    * Handle an error event for a missing local generator load by unsetting
  1234.    * the microsummaries for bookmarks using this generator so we don't
  1235.    * repeatedly try to reinstall the generator, creating too much traffic
  1236.    * to the website from which we downloaded it.
  1237.    *
  1238.    * @param resource
  1239.    *        the nsIMicrosummaryResource representing the downloaded generator
  1240.    *
  1241.    */
  1242.   _handleMissingGeneratorError: function MS__handleMissingGeneratorError(resource) {
  1243.     LOG("reinstall failed; removing microsummaries " + this.generator.uri.spec);
  1244.     var bookmarks = this._mss.getBookmarks();
  1245.     while (bookmarks.hasMoreElements()) {
  1246.       var bookmarkID = bookmarks.getNext();
  1247.       var microsummary = this._mss.getMicrosummary(bookmarkID);
  1248.       if (microsummary.generator.uri.equals(this.generator.uri)) {
  1249.         LOG("removing microsummary for " + microsummary.pageURI.spec);
  1250.         this._mss.removeMicrosummary(bookmarkID);
  1251.       }
  1252.     }
  1253.   }
  1254.  
  1255. };
  1256.  
  1257.  
  1258.  
  1259.  
  1260.  
  1261. function MicrosummaryGenerator() {}
  1262.  
  1263. MicrosummaryGenerator.prototype = {
  1264.  
  1265.   // IO Service
  1266.   __ios: null,
  1267.   get _ios() {
  1268.     if (!this.__ios)
  1269.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1270.                    getService(Ci.nsIIOService);
  1271.     return this.__ios;
  1272.   },
  1273.  
  1274.   interfaces: [Ci.nsIMicrosummaryGenerator, Ci.nsISupports],
  1275.  
  1276.   // nsISupports
  1277.  
  1278.   QueryInterface: function (iid) {
  1279.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  1280.     if (!iid.equals(Ci.nsIMicrosummaryGenerator) &&
  1281.         !iid.equals(Ci.nsISupports))
  1282.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1283.     return this;
  1284.   },
  1285.  
  1286.   // nsIMicrosummaryGenerator
  1287.  
  1288.   // Normally this is just the URL from which we download the generator,
  1289.   // but for generators stored in the app or profile generators directory
  1290.   // it's the value of the generator tag's "uri" attribute (or its local URI
  1291.   // should the "uri" attribute be missing).
  1292.   _uri: null,
  1293.   get uri() { return this._uri || this.localURI },
  1294.   set uri(newValue) { this._uri = newValue },
  1295.  
  1296.   // For generators bundled with the browser or installed by the user,
  1297.   // the local URI is the URI of the local file containing the generator XML.
  1298.   _localURI: null,
  1299.   get localURI() { return this._localURI },
  1300.   set localURI(newValue) { this._localURI = newValue },
  1301.  
  1302.   _name: null,
  1303.   get name() { return this._name },
  1304.   set name(newValue) { this._name = newValue },
  1305.  
  1306.   _template: null,
  1307.   get template() { return this._template },
  1308.   set template(newValue) { this._template = newValue },
  1309.  
  1310.   _content: null,
  1311.   get content() { return this._content },
  1312.   set content(newValue) { this._content = newValue },
  1313.  
  1314.   _loaded: false,
  1315.   get loaded() { return this._loaded },
  1316.   set loaded(newValue) { this._loaded = newValue },
  1317.  
  1318.   _rules: null,
  1319.  
  1320.   /**
  1321.    * Determines whether or not the generator applies to a given URI.
  1322.    * By default, the generator does not apply to any URI.  In order for it
  1323.    * to apply to a URI, the URI must match one or more of the generator's
  1324.    * "include" rules and not match any of the generator's "exclude" rules.
  1325.    *
  1326.    * @param   uri
  1327.    *          the URI to test to see if this generator applies to it
  1328.    *
  1329.    * @returns boolean
  1330.    *          whether or not the generator applies to the given URI
  1331.    *
  1332.    */
  1333.   appliesToURI: function(uri) {
  1334.     var applies = false;
  1335.  
  1336.     for ( var i = 0 ; i < this._rules.length ; i++ ) {
  1337.       var rule = this._rules[i];
  1338.  
  1339.       switch (rule.type) {
  1340.       case "include":
  1341.         if (rule.regexp.test(uri.spec))
  1342.           applies = true;
  1343.         break;
  1344.       case "exclude":
  1345.         if (rule.regexp.test(uri.spec))
  1346.           return false;
  1347.         break;
  1348.       }
  1349.     }
  1350.  
  1351.     return applies;
  1352.   },
  1353.  
  1354.   get needsPageContent() {
  1355.     if (this.template)
  1356.       return true;
  1357.     else if (this.content)
  1358.       return false;
  1359.     else
  1360.       throw("needsPageContent called on uninitialized microsummary generator");
  1361.   },
  1362.  
  1363.   /**
  1364.    * Initializes a generator from text content.  Generators initialized
  1365.    * from text content merely return that content when their generate() method
  1366.    * gets called.
  1367.    *
  1368.    * @param   text
  1369.    *          the text content
  1370.    *
  1371.    */
  1372.   initFromText: function(text) {
  1373.     this.content = text;
  1374.     this.loaded = true;
  1375.   },
  1376.  
  1377.   /**
  1378.    * Initializes a generator from an XML description of it.
  1379.    * 
  1380.    * @param   xmlDocument
  1381.    *          An XMLDocument object describing a microsummary generator.
  1382.    *
  1383.    */
  1384.   initFromXML: function(xmlDocument) {
  1385.     // XXX Make sure the argument is a valid generator XML document.
  1386.  
  1387.     // XXX I would have wanted to retrieve the info from the XML via E4X,
  1388.     // but we'll need to pass the XSLT transform sheet to the XSLT processor,
  1389.     // and the processor can't deal with an E4X-wrapped template node.
  1390.  
  1391.     // XXX Right now the code retrieves the first "generator" element
  1392.     // in the microsummaries namespace, regardless of whether or not
  1393.     // it's the root element.  Should it matter?
  1394.     var generatorNode = xmlDocument.getElementsByTagNameNS(MICSUM_NS, "generator")[0];
  1395.     if (!generatorNode)
  1396.       throw Components.results.NS_ERROR_FAILURE;
  1397.  
  1398.     this.name = generatorNode.getAttribute("name");
  1399.  
  1400.     // If this is a local generator (i.e. it has a local URI), then we have
  1401.     // to retrieve its URI from the "uri" attribute of its generator tag.
  1402.     if (this.localURI && generatorNode.hasAttribute("uri")) {
  1403.       this.uri = this._ios.newURI(generatorNode.getAttribute("uri"), null, null);
  1404.     }
  1405.  
  1406.     function getFirstChildByTagName(tagName, parentNode, namespace) {
  1407.       var nodeList = parentNode.getElementsByTagNameNS(namespace, tagName);
  1408.       for (var i = 0; i < nodeList.length; i++) {
  1409.         // Make sure that the node is a direct descendent of the generator node
  1410.         if (nodeList[i].parentNode == parentNode)
  1411.           return nodeList[i];
  1412.       }
  1413.       return null;
  1414.     }
  1415.  
  1416.     // Slurp the include/exclude rules that determine the pages to which
  1417.     // this generator applies.  Order is important, so we add the rules
  1418.     // in the order in which they appear in the XML.
  1419.     this._rules = [];
  1420.     var pages = getFirstChildByTagName("pages", generatorNode, MICSUM_NS);
  1421.     if (pages) {
  1422.       // XXX Make sure the pages tag exists.
  1423.       for ( var i = 0; i < pages.childNodes.length ; i++ ) {
  1424.         var node = pages.childNodes[i];
  1425.         if (node.nodeType != node.ELEMENT_NODE ||
  1426.             node.namespaceURI != MICSUM_NS ||
  1427.             (node.nodeName != "include" && node.nodeName != "exclude"))
  1428.           continue;
  1429.         var urlRegexp = node.textContent.replace(/^\s+|\s+$/g, "");
  1430.         this._rules.push({ type: node.nodeName, regexp: new RegExp(urlRegexp) });
  1431.       }
  1432.     }
  1433.  
  1434.     // allow the generators to set individual update values (even varying
  1435.     // depending on certain XPath expressions)
  1436.     var update = getFirstChildByTagName("update", generatorNode, MICSUM_NS);
  1437.     if (update) {
  1438.       function _parseInterval(string) {
  1439.         // convert from minute fractions to milliseconds
  1440.         // and ensure a minimum value of 1 minute
  1441.         return Math.round(Math.max(parseFloat(string) || 0, 1) * 60 * 1000);
  1442.       }
  1443.  
  1444.       this._unconditionalUpdateInterval =
  1445.         update.hasAttribute("interval") ?
  1446.         _parseInterval(update.getAttribute("interval")) : null;
  1447.  
  1448.       // collect the <condition expression="XPath Expression" interval="time"/> clauses
  1449.       this._updateIntervals = new Array();
  1450.       for (i = 0; i < update.childNodes.length; i++) {
  1451.         node = update.childNodes[i];
  1452.         if (node.nodeType != node.ELEMENT_NODE || node.namespaceURI != MICSUM_NS ||
  1453.             node.nodeName != "condition")
  1454.           continue;
  1455.         if (!node.getAttribute("expression") || !node.getAttribute("interval")) {
  1456.           LOG("ignoring incomplete conditional update interval for " + this.uri.spec);
  1457.           continue;
  1458.         }
  1459.         this._updateIntervals.push({
  1460.           expression: node.getAttribute("expression"),
  1461.           interval: _parseInterval(node.getAttribute("interval"))
  1462.         });
  1463.       }
  1464.     }
  1465.  
  1466.     var templateNode = getFirstChildByTagName("template", generatorNode, MICSUM_NS);
  1467.     if (templateNode) {
  1468.       this.template = getFirstChildByTagName("transform", templateNode, XSLT_NS) ||
  1469.                       getFirstChildByTagName("stylesheet", templateNode, XSLT_NS);
  1470.     }
  1471.     // XXX Make sure the template is a valid XSL transform sheet.
  1472.  
  1473.     this.loaded = true;
  1474.   },
  1475.  
  1476.   generateMicrosummary: function MSD_generateMicrosummary(pageContent) {
  1477.  
  1478.     var content;
  1479.  
  1480.     if (this.content) {
  1481.       content = this.content;
  1482.     } else if (this.template) {
  1483.       content = this._processTemplate(pageContent);
  1484.     } else
  1485.       throw("generateMicrosummary called on uninitialized microsummary generator");
  1486.  
  1487.     // Clean up the output
  1488.     content = content.replace(/^\s+|\s+$/g, "");
  1489.     if (content.length > MAX_SUMMARY_LENGTH) 
  1490.       content = content.substring(0, MAX_SUMMARY_LENGTH);
  1491.  
  1492.     return content;
  1493.   },
  1494.  
  1495.   calculateUpdateInterval: function MSD_calculateUpdateInterval(doc) {
  1496.     if (this.content || !this._updateIntervals || !doc)
  1497.       return null;
  1498.  
  1499.     for (var i = 0; i < this._updateIntervals.length; i++) {
  1500.       try {
  1501.         if (doc.evaluate(this._updateIntervals[i].expression, doc, null,
  1502.                          Ci.nsIDOMXPathResult.BOOLEAN_TYPE, null).booleanValue)
  1503.           return this._updateIntervals[i].interval;
  1504.       }
  1505.       catch (ex) {
  1506.         Components.utils.reportError(ex);
  1507.         // remove the offending conditional update interval
  1508.         this._updateIntervals.splice(i--, 1);
  1509.       }
  1510.     }
  1511.  
  1512.     return this._unconditionalUpdateInterval;
  1513.   },
  1514.  
  1515.   _processTemplate: function MSD__processTemplate(doc) {
  1516.     LOG("processing template " + this.template + " against document " + doc);
  1517.  
  1518.     // XXX Should we just have one global instance of the processor?
  1519.     var processor = Cc["@mozilla.org/document-transformer;1?type=xslt"].
  1520.                     createInstance(Ci.nsIXSLTProcessor);
  1521.  
  1522.     // Turn off document loading of all kinds (document(), <include>, <import>)
  1523.     // for security (otherwise local generators would be able to load local files).
  1524.     processor.flags |= Ci.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
  1525.  
  1526.     processor.importStylesheet(this.template);
  1527.     var fragment = processor.transformToFragment(doc, doc);
  1528.  
  1529.     LOG("template processing result: " + fragment.textContent);
  1530.  
  1531.     // XXX When we support HTML microsummaries we'll need to do something
  1532.     // more sophisticated than just returning the text content of the fragment.
  1533.     return fragment.textContent;
  1534.   }
  1535. };
  1536.  
  1537.  
  1538.  
  1539.  
  1540.  
  1541. // Microsummary sets are collections of microsummaries.  They allow callers
  1542. // to register themselves as observers of the set, and when any microsummary
  1543. // in the set changes, the observers get notified.  Thus a caller can observe
  1544. // the set instead of each individual microsummary.
  1545.  
  1546. function MicrosummarySet() {
  1547.   this._observers = [];
  1548.   this._elements = [];
  1549. }
  1550.  
  1551. MicrosummarySet.prototype = {
  1552.  
  1553.   // IO Service
  1554.   __ios: null,
  1555.   get _ios() {
  1556.     if (!this.__ios)
  1557.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1558.                    getService(Ci.nsIIOService);
  1559.     return this.__ios;
  1560.   },
  1561.  
  1562.   interfaces: [Ci.nsIMicrosummarySet,
  1563.                Ci.nsIMicrosummaryObserver,
  1564.                Ci.nsISupports],
  1565.  
  1566.   QueryInterface: function (iid) {
  1567.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  1568.     if (!iid.equals(Ci.nsIMicrosummarySet) &&
  1569.         !iid.equals(Ci.nsIMicrosummaryObserver) &&
  1570.         !iid.equals(Ci.nsISupports))
  1571.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1572.     return this;
  1573.   },
  1574.  
  1575.   _observers: null,
  1576.   _elements: null,
  1577.  
  1578.   // nsIMicrosummaryObserver
  1579.  
  1580.   onContentLoaded: function MSSet_onContentLoaded(microsummary) {
  1581.     for ( var i = 0; i < this._observers.length; i++ )
  1582.       this._observers[i].onContentLoaded(microsummary);
  1583.   },
  1584.  
  1585.   // nsIMicrosummarySet
  1586.  
  1587.   addObserver: function MSSet_addObserver(observer) {
  1588.     if (this._observers.length == 0) {
  1589.       for ( var i = 0 ; i < this._elements.length ; i++ )
  1590.         this._elements[i].addObserver(this);
  1591.     }
  1592.  
  1593.     // Register the observer, but only if it isn't already registered,
  1594.     // so that we don't call the same observer twice for any given change.
  1595.     if (this._observers.indexOf(observer) == -1)
  1596.       this._observers.push(observer);
  1597.   },
  1598.   
  1599.   removeObserver: function MSSet_removeObserver(observer) {
  1600.     //NS_ASSERT(this._observers.indexOf(observer) != -1,
  1601.     //          "can't remove microsummary observer " + observer + ": not registered");
  1602.   
  1603.     //this._observers =
  1604.     //  this._observers.filter(function(i) { observer != i });
  1605.     if (this._observers.indexOf(observer) != -1)
  1606.       this._observers.splice(this._observers.indexOf(observer), 1);
  1607.     
  1608.     if (this._observers.length == 0) {
  1609.       for ( var i = 0 ; i < this._elements.length ; i++ )
  1610.         this._elements[i].removeObserver(this);
  1611.     }
  1612.   },
  1613.  
  1614.   extractFromPage: function MSSet_extractFromPage(resource) {
  1615.     if (!resource.isXML && resource.contentType != "text/html")
  1616.       throw("page is neither HTML nor XML");
  1617.  
  1618.     // XXX Handle XML documents, whose microsummaries are specified
  1619.     // via processing instructions.
  1620.  
  1621.     var links = resource.content.getElementsByTagName("link");
  1622.     for ( var i = 0; i < links.length; i++ ) {
  1623.       var link = links[i];
  1624.  
  1625.       if(!link.hasAttribute("rel"))
  1626.         continue;
  1627.  
  1628.       var relAttr = link.getAttribute("rel");
  1629.  
  1630.       // The attribute's value can be a space-separated list of link types,
  1631.       // check to see if "microsummary" is one of them.
  1632.       var linkTypes = relAttr.split(/\s+/);
  1633.       if (!linkTypes.some( function(v) { return v.toLowerCase() == "microsummary"; }))
  1634.         continue;
  1635.  
  1636.  
  1637.       // Look for a TITLE attribute to give the generator a nice name in the UI.
  1638.       var linkTitle = link.getAttribute("title");
  1639.  
  1640.  
  1641.       // Unlike the "href" attribute, the "href" property contains
  1642.       // an absolute URI spec, so we use it here to create the URI.
  1643.       var generatorURI = this._ios.newURI(link.href,
  1644.                                           resource.content.characterSet,
  1645.                                           null);
  1646.  
  1647.       try {
  1648.         const securityManager = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1649.                                 getService(Ci.nsIScriptSecurityManager);
  1650.         securityManager.checkLoadURI(resource.uri,
  1651.                                      generatorURI,
  1652.                                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
  1653.       }
  1654.       catch(e) {
  1655.         LOG("can't load generator " + generatorURI.spec + " from page " +
  1656.             resource.uri.spec + ": " + e);
  1657.         continue;
  1658.       }
  1659.  
  1660.       var microsummary = new Microsummary(resource.uri, null);
  1661.       microsummary.generator.name = linkTitle;
  1662.       microsummary.generator.uri  = generatorURI;
  1663.       this.AppendElement(microsummary);
  1664.     }
  1665.   },
  1666.  
  1667.   // XXX Turn this into a complete implementation of nsICollection?
  1668.   AppendElement: function MSSet_AppendElement(element) {
  1669.     // Query the element to a microsummary.
  1670.     // XXX Should we NS_ASSERT if this fails?
  1671.     element = element.QueryInterface(Ci.nsIMicrosummary);
  1672.  
  1673.     if (this._elements.indexOf(element) == -1) {
  1674.       this._elements.push(element);
  1675.       element.addObserver(this);
  1676.     }
  1677.  
  1678.     // Notify observers that an element has been appended.
  1679.     for ( var i = 0; i < this._observers.length; i++ )
  1680.       this._observers[i].onElementAppended(element);
  1681.   },
  1682.  
  1683.   Enumerate: function MSSet_Enumerate() {
  1684.     return new ArrayEnumerator(this._elements);
  1685.   }
  1686. };
  1687.  
  1688.  
  1689.  
  1690.  
  1691.  
  1692. /**
  1693.  * An enumeration of items in a JS array.
  1694.  * @constructor
  1695.  */
  1696. function ArrayEnumerator(aItems) {
  1697.   this._index = 0;
  1698.   if (aItems) {
  1699.     for (var i = 0; i < aItems.length; ++i) {
  1700.       if (!aItems[i])
  1701.         aItems.splice(i, 1);      
  1702.     }
  1703.   }
  1704.   this._contents = aItems;
  1705. }
  1706.  
  1707. ArrayEnumerator.prototype = {
  1708.   interfaces: [Ci.nsISimpleEnumerator, Ci.nsISupports],
  1709.  
  1710.   QueryInterface: function (iid) {
  1711.     //if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  1712.     if (!iid.equals(Ci.nsISimpleEnumerator) &&
  1713.         !iid.equals(Ci.nsISupports))
  1714.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1715.     return this;
  1716.   },
  1717.  
  1718.   _index: 0,
  1719.   _contents: [],
  1720.   
  1721.   hasMoreElements: function() {
  1722.     return this._index < this._contents.length;
  1723.   },
  1724.   
  1725.   getNext: function() {
  1726.     return this._contents[this._index++];      
  1727.   }
  1728. };
  1729.  
  1730.  
  1731.  
  1732.  
  1733.  
  1734. /**
  1735.  * Outputs aText to the JavaScript console as well as to stdout if the
  1736.  * microsummary logging pref (browser.microsummary.log) is set to true.
  1737.  * 
  1738.  * @param aText
  1739.  *        the text to log
  1740.  */
  1741. function LOG(aText) {
  1742.   if (getPref("browser.microsummary.log", false)) {
  1743.     dump("*** Microsummaries: " +  aText + "\n");
  1744.     var consoleService = Cc["@mozilla.org/consoleservice;1"].
  1745.                          getService(Ci.nsIConsoleService);
  1746.     consoleService.logStringMessage(aText);
  1747.   }
  1748. }
  1749.  
  1750.  
  1751.  
  1752.  
  1753.  
  1754. /**
  1755.  * A resource (page, microsummary, generator, etc.) identifiable by URI.
  1756.  * This object abstracts away much of the code for loading resources
  1757.  * and parsing their content if they are XML or HTML.
  1758.  * 
  1759.  * @constructor
  1760.  * 
  1761.  * @param   uri
  1762.  *          the location of the resource
  1763.  *
  1764.  */
  1765. function MicrosummaryResource(uri) {
  1766.   // Make sure we're not loading javascript: or data: URLs, which could
  1767.   // take advantage of the load to run code with chrome: privileges.
  1768.   // XXX Perhaps use nsIScriptSecurityManager.checkLoadURI instead.
  1769.   if (uri.scheme != "http" && uri.scheme != "https" && uri.scheme != "file")
  1770.     throw NS_ERROR_DOM_BAD_URI;
  1771.  
  1772.   this._uri = uri;
  1773. }
  1774.  
  1775. MicrosummaryResource.prototype = {
  1776.   // IO Service
  1777.   __ios: null,
  1778.   get _ios() {
  1779.     if (!this.__ios)
  1780.       this.__ios = Cc["@mozilla.org/network/io-service;1"].
  1781.                    getService(Ci.nsIIOService);
  1782.     return this.__ios;
  1783.   },
  1784.  
  1785.   _uri: null,
  1786.   get uri() {
  1787.     return this._uri;
  1788.   },
  1789.  
  1790.   _content: null,
  1791.   get content() {
  1792.     return this._content;
  1793.   },
  1794.  
  1795.   _contentType: null,
  1796.   get contentType() {
  1797.     return this._contentType;
  1798.   },
  1799.  
  1800.   _isXML: false,
  1801.   get isXML() {
  1802.     return this._isXML;
  1803.   },
  1804.  
  1805.   // A function to call when we finish loading/parsing the resource.
  1806.   _loadCallback: null,
  1807.  
  1808.   // A function to call if we get an error while loading/parsing the resource.
  1809.   _errorCallback: null,
  1810.  
  1811.   // A hidden iframe to parse HTML content.
  1812.   _iframe: null,
  1813.  
  1814.   // Implement notification callback interfaces so we can suppress UI
  1815.   // and abort loads for bad SSL certs and HTTP authorization requests.
  1816.   
  1817.   // Interfaces this component implements.
  1818.   interfaces: [Ci.nsIBadCertListener,
  1819.                Ci.nsIAuthPromptProvider,
  1820.                Ci.nsIAuthPrompt,
  1821.                Ci.nsIProgressEventSink,
  1822.                Ci.nsIInterfaceRequestor,
  1823.                Ci.nsISupports],
  1824.  
  1825.   // nsISupports
  1826.  
  1827.   QueryInterface: function MSR_QueryInterface(iid) {
  1828.     if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
  1829.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1830.  
  1831.     return this;
  1832.   },
  1833.  
  1834.   // nsIInterfaceRequestor
  1835.   
  1836.   getInterface: function MSR_getInterface(iid) {
  1837.     return this.QueryInterface(iid);
  1838.   },
  1839.  
  1840.   // nsIBadCertListener
  1841.  
  1842.   // Suppress UI and abort secure loads from servers with bad SSL certificates.
  1843.   
  1844.   confirmUnknownIssuer: function MSR_confirmUnknownIssuer(socketInfo, cert, certAddType) {
  1845.     return false;
  1846.   },
  1847.  
  1848.   confirmMismatchDomain: function MSR_confirmMismatchDomain(socketInfo, targetURL, cert) {
  1849.     return false;
  1850.   },
  1851.  
  1852.   confirmCertExpired: function MSR_confirmCertExpired(socketInfo, cert) {
  1853.     return false;
  1854.   },
  1855.  
  1856.   notifyCrlNextupdate: function MSR_notifyCrlNextupdate(socketInfo, targetURL, cert) {
  1857.   },
  1858.  
  1859.   // nsIAuthPromptProvider
  1860.   
  1861.   // Suppress UI and abort loads for files secured by HTTP authentication.
  1862.  
  1863.   // HTTP auth requests appear to succeed when we cancel them (since the server
  1864.   // redirects us to a "you're not authorized" page), so we have to set a flag
  1865.   // to let the load handler know to treat the load as a failure.
  1866.   __httpAuthFailed: false,
  1867.   get _httpAuthFailed()         { return this.__httpAuthFailed },
  1868.   set _httpAuthFailed(newValue) { this.__httpAuthFailed = newValue },
  1869.  
  1870.   getAuthPrompt: function(aPromptReason) {
  1871.     this._httpAuthFailed = true;
  1872.     throw Components.results.NS_ERROR_NOT_AVAILABLE;
  1873.   },
  1874.  
  1875.   // nsIAuthPrompt
  1876.  
  1877.   // XXX If necko always requests nsIAuthPromptProvider before requesting
  1878.   // nsIAuthPrompt, then we probably only have to implement the provider.
  1879.  
  1880.   prompt: function(dialogTitle, text, passwordRealm, savePassword, defaultText, result) {
  1881.     this._httpAuthFailed = true;
  1882.     return false;
  1883.   },
  1884.  
  1885.   promptUsernameAndPassword: function(dialogTitle, text, passwordRealm, savePassword, user, pwd) {
  1886.     this._httpAuthFailed = true;
  1887.     return false;
  1888.   },
  1889.  
  1890.   promptPassword: function(dialogTitle, text, passwordRealm, savePassword, pwd) {
  1891.     this._httpAuthFailed = true;
  1892.     return false;
  1893.   },
  1894.  
  1895.   // XXX We implement nsIProgressEventSink because otherwise bug 253127
  1896.   // would cause too many extraneous errors to get reported to the console.
  1897.   // Fortunately this doesn't screw up XMLHttpRequest, because it ensures
  1898.   // that its implementation of nsIProgressEventSink will always get called
  1899.   // in addition to whatever notification callbacks we set on the channel
  1900.   // (this is not true for most other interfaces, so we should be conservative
  1901.   // about what we implement/override, even in the face of bug 253127).
  1902.  
  1903.   // nsIProgressEventSink
  1904.  
  1905.   onProgress: function(aRequest, aContext, aProgress, aProgressMax) {},
  1906.   onStatus: function(aRequest, aContext, aStatus, aStatusArg) {},
  1907.  
  1908.   /**
  1909.    * Initialize the resource from an existing DOM document object.
  1910.    * 
  1911.    * @param   document
  1912.    *          a DOM document object
  1913.    *
  1914.    */
  1915.   initFromDocument: function MSR_initFromDocument(document) {
  1916.     this._content = document;
  1917.     this._contentType = document.contentType;
  1918.  
  1919.     // Normally we set this property based on whether or not
  1920.     // XMLHttpRequest parsed the content into an XML document object,
  1921.     // but since we already have the content, we have to analyze
  1922.     // its content type ourselves to see if it is XML.
  1923.     this._isXML = (this.contentType == "text/xml" ||
  1924.                    this.contentType == "application/xml" ||
  1925.                    /^.+\/.+\+xml$/.test(this.contentType));
  1926.   },
  1927.  
  1928.   /**
  1929.    * Destroy references to avoid leak-causing cycles.  Instantiators must call
  1930.    * this method on all instances they instantiate once they're done with them.
  1931.    *
  1932.    */
  1933.   destroy: function MSR_destroy() {
  1934.     this._uri = null;
  1935.     this._content = null;
  1936.     this._loadCallback = null;
  1937.     this._errorCallback = null;
  1938.     this._loadTimer = null;
  1939.     this._httpAuthFailed = false;
  1940.     if (this._iframe) {
  1941.       if (this._iframe && this._iframe.parentNode)
  1942.         this._iframe.parentNode.removeChild(this._iframe);
  1943.       this._iframe = null;
  1944.     }
  1945.   },
  1946.  
  1947.   /**
  1948.    * Load the resource.
  1949.    * 
  1950.    * @param   loadCallback
  1951.    *          a function to invoke when the resource finishes loading
  1952.    * @param   errorCallback
  1953.    *          a function to invoke when an error occurs during the load
  1954.    *
  1955.    */
  1956.   load: function MSR_load(loadCallback, errorCallback) {
  1957.     LOG(this.uri.spec + " loading");
  1958.   
  1959.     this._loadCallback = loadCallback;
  1960.     this._errorCallback = errorCallback;
  1961.  
  1962.     var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
  1963.   
  1964.     var loadHandler = {
  1965.       _self: this,
  1966.       handleEvent: function MSR_loadHandler_handleEvent(event) {
  1967.         if (this._self._loadTimer)
  1968.           this._self._loadTimer.cancel();
  1969.  
  1970.         if (this._self._httpAuthFailed) {
  1971.           // Technically the request succeeded, but we treat it as a failure,
  1972.           // since we aren't able to handle HTTP authentication.
  1973.           LOG(this._self.uri.spec + " load failed; HTTP auth required");
  1974.           try     { this._self._handleError(event) }
  1975.           finally { this._self = null }
  1976.         }
  1977.         else if (event.target.channel.contentType == "multipart/x-mixed-replace") {
  1978.           // Technically the request succeeded, but we treat it as a failure,
  1979.           // since we aren't able to handle multipart content.
  1980.           LOG(this._self.uri.spec + " load failed; contains multipart content");
  1981.           try     { this._self._handleError(event) }
  1982.           finally { this._self = null }
  1983.         }
  1984.         else {
  1985.           LOG(this._self.uri.spec + " load succeeded; invoking callback");
  1986.           try     { this._self._handleLoad(event) }
  1987.           finally { this._self = null }
  1988.         }
  1989.       }
  1990.     };
  1991.  
  1992.     var errorHandler = {
  1993.       _self: this,
  1994.       handleEvent: function MSR_errorHandler_handleEvent(event) {
  1995.         if (this._self._loadTimer)
  1996.           this._self._loadTimer.cancel();
  1997.  
  1998.         LOG(this._self.uri.spec + " load failed");
  1999.         try     { this._self._handleError(event) }
  2000.         finally { this._self = null }
  2001.       }
  2002.     };
  2003.  
  2004.     // cancel loads that take too long
  2005.     // timeout specified in seconds at browser.microsummary.requestTimeout,
  2006.     // or 300 seconds (five minutes)
  2007.     var timeout = getPref("browser.microsummary.requestTimeout", 300) * 1000;
  2008.     var timerObserver = {
  2009.       _self: this,
  2010.       observe: function MSR_timerObserver_observe() {
  2011.         LOG("timeout loading microsummary resource " + this._self.uri.spec + ", aborting request");
  2012.         request.abort();
  2013.         try     { this._self.destroy() }
  2014.         finally { this._self = null }
  2015.       }
  2016.     };
  2017.     this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  2018.     this._loadTimer.init(timerObserver, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
  2019.  
  2020.     request = request.QueryInterface(Ci.nsIDOMEventTarget);
  2021.     request.addEventListener("load", loadHandler, false);
  2022.     request.addEventListener("error", errorHandler, false);
  2023.     
  2024.     request = request.QueryInterface(Ci.nsIXMLHttpRequest);
  2025.     request.open("GET", this.uri.spec, true);
  2026.     request.setRequestHeader("X-Moz", "microsummary");
  2027.  
  2028.     // Register ourselves as a listener for notification callbacks so we
  2029.     // can handle authorization requests and SSL issues like cert mismatches.
  2030.     // XMLHttpRequest will handle the notifications we don't handle.
  2031.     request.channel.notificationCallbacks = this;
  2032.  
  2033.     // If this is a bookmarked resource, and the bookmarks service recorded
  2034.     // its charset in the bookmarks datastore the last time the user visited it,
  2035.     // then specify the charset in the channel so XMLHttpRequest loads
  2036.     // the resource correctly.
  2037.     try {
  2038.       var resolver = Cc["@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1"].
  2039.                      getService(Ci.nsICharsetResolver);
  2040.       if (resolver) {
  2041.         var charset = resolver.requestCharset(null, request.channel, {}, {});
  2042.         if (charset != "");
  2043.           request.channel.contentCharset = charset;
  2044.       }
  2045.     }
  2046.     catch(ex) {}
  2047.  
  2048.     request.send(null);
  2049.   },
  2050.  
  2051.   _handleLoad: function MSR__handleLoad(event) {
  2052.     var request = event.target;
  2053.  
  2054.     if (request.responseXML) {
  2055.       this._isXML = true;
  2056.       // XXX Figure out the parsererror format and log a specific error.
  2057.       if (request.responseXML.documentElement.nodeName == "parsererror")
  2058.         throw(request.channel.originalURI.spec + " contains invalid XML");
  2059.       this._content = request.responseXML;
  2060.       this._contentType = request.channel.contentType;
  2061.       this._loadCallback(this);
  2062.     }
  2063.  
  2064.     else if (request.channel.contentType == "text/html") {
  2065.       this._parse(request.responseText);
  2066.     }
  2067.  
  2068.     else {
  2069.       // This catches text/plain as well as any other content types
  2070.       // not accounted for by the content type-specific code above.
  2071.       this._content = request.responseText;
  2072.       this._contentType = request.channel.contentType;
  2073.       this._loadCallback(this);
  2074.     }
  2075.   },
  2076.   
  2077.   _handleError: function MSR__handleError(event) {
  2078.     // Call the error callback, then destroy ourselves to prevent memory leaks.
  2079.     try     { if (this._errorCallback) this._errorCallback() }
  2080.     finally { this.destroy() }
  2081.   },
  2082.  
  2083.   /**
  2084.    * Parse a string of HTML text.  Used by _load() when it retrieves HTML.
  2085.    * We do this via hidden XUL iframes, which according to bz is the best way
  2086.    * to do it currently, since bug 102699 is hard to fix.
  2087.    * 
  2088.    * @param   htmlText
  2089.    *          a string containing the HTML content
  2090.    *
  2091.    */
  2092.   _parse: function MSR__parse(htmlText) {
  2093.     // Find a window to stick our hidden iframe into.
  2094.     var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
  2095.                          getService(Ci.nsIWindowMediator);
  2096.     var window = windowMediator.getMostRecentWindow("navigator:browser");
  2097.     // XXX We can use other windows, too, so perhaps we should try to get
  2098.     // some other window if there's no browser window open.  Perhaps we should
  2099.     // even prefer other windows, since there's less chance of any browser
  2100.     // window machinery like throbbers treating our load like one initiated
  2101.     // by the user.
  2102.     if (!window)
  2103.       throw(this._uri.spec + " can't parse; no browser window");
  2104.     var document = window.document;
  2105.     var rootElement = document.documentElement;
  2106.   
  2107.     // Create an iframe, make it hidden, and secure it against untrusted content.
  2108.     this._iframe = document.createElement('iframe');
  2109.     this._iframe.setAttribute("collapsed", true);
  2110.     this._iframe.setAttribute("type", "content");
  2111.   
  2112.     // Insert the iframe into the window, creating the doc shell.
  2113.     rootElement.appendChild(this._iframe);
  2114.  
  2115.     // When we insert the iframe into the window, it immediately starts loading
  2116.     // about:blank, which we don't need and could even hurt us (for example
  2117.     // by triggering bugs like bug 344305), so cancel that load.
  2118.     var webNav = this._iframe.docShell.QueryInterface(Ci.nsIWebNavigation);
  2119.     webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);
  2120.  
  2121.     // Turn off JavaScript and auth dialogs for security and other things
  2122.     // to reduce network load.
  2123.     // XXX We should also turn off CSS.
  2124.     this._iframe.docShell.allowJavascript = false;
  2125.     this._iframe.docShell.allowAuth = false;
  2126.     this._iframe.docShell.allowPlugins = false;
  2127.     this._iframe.docShell.allowMetaRedirects = false;
  2128.     this._iframe.docShell.allowSubframes = false;
  2129.     this._iframe.docShell.allowImages = false;
  2130.   
  2131.     var parseHandler = {
  2132.       _self: this,
  2133.       handleEvent: function MSR_parseHandler_handleEvent(event) {
  2134.         event.target.removeEventListener("DOMContentLoaded", this, false);
  2135.         try     { this._self._handleParse(event) }
  2136.         finally { this._self = null }
  2137.       }
  2138.     };
  2139.  
  2140.     // Convert the HTML text into an input stream.
  2141.     var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  2142.                     createInstance(Ci.nsIScriptableUnicodeConverter);
  2143.     converter.charset = "UTF-8";
  2144.     var stream = converter.convertToInputStream(htmlText);
  2145.  
  2146.     // Set up a channel to load the input stream.
  2147.     var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
  2148.                   createInstance(Ci.nsIInputStreamChannel);
  2149.     channel.setURI(this._uri);
  2150.     channel.contentStream = stream;
  2151.  
  2152.     // Load in the background so we don't trigger web progress listeners.
  2153.     var request = channel.QueryInterface(Ci.nsIRequest);
  2154.     request.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND;
  2155.  
  2156.     // Specify the content type since we're not loading content from a server,
  2157.     // so it won't get specified for us, and if we don't specify it ourselves,
  2158.     // then Firefox will prompt the user to download content of "unknown type".
  2159.     var baseChannel = channel.QueryInterface(Ci.nsIChannel);
  2160.     baseChannel.contentType = "text/html";
  2161.  
  2162.     // Load as UTF-8, which it'll always be, because XMLHttpRequest converts
  2163.     // the text (i.e. XMLHTTPRequest.responseText) from its original charset
  2164.     // to UTF-16, then the string input stream component converts it to UTF-8.
  2165.     baseChannel.contentCharset = "UTF-8";
  2166.  
  2167.     // Register the parse handler as a load event listener and start the load.
  2168.     // Listen for "DOMContentLoaded" instead of "load" because background loads
  2169.     // don't fire "load" events.
  2170.     this._iframe.addEventListener("DOMContentLoaded", parseHandler, true);
  2171.     var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
  2172.     uriLoader.openURI(channel, true, this._iframe.docShell);
  2173.   },
  2174.  
  2175.   /**
  2176.    * Handle a load event for the iframe-based parser.
  2177.    * 
  2178.    * @param   event
  2179.    *          the event object representing the load event
  2180.    *
  2181.    */
  2182.   _handleParse: function MSR__handleParse(event) {
  2183.     // XXX Make sure the parse was successful?
  2184.  
  2185.     this._content = this._iframe.contentDocument;
  2186.     this._contentType = this._iframe.contentDocument.contentType;
  2187.     this._loadCallback(this);
  2188.   }
  2189.  
  2190. };
  2191.  
  2192. /**
  2193.  * Get a resource currently loaded into a browser window.  Checks windows
  2194.  * one at a time, starting with the frontmost (a.k.a. most recent) one.
  2195.  * 
  2196.  * @param   uri
  2197.  *          the URI of the resource
  2198.  *
  2199.  * @returns a Resource object, if the resource is currently loaded
  2200.  *          into a browser window; otherwise null
  2201.  *
  2202.  */
  2203. function getLoadedMicrosummaryResource(uri) {
  2204.   var mediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  2205.                  getService(Ci.nsIWindowMediator);
  2206.  
  2207.   // Apparently the Z order enumerator is broken on Linux per bug 156333.
  2208.   //var windows = mediator.getZOrderDOMWindowEnumerator("navigator:browser", true);
  2209.   var windows = mediator.getEnumerator("navigator:browser");
  2210.  
  2211.   while (windows.hasMoreElements()) {
  2212.     var win = windows.getNext();
  2213.     var tabBrowser = win.document.getElementById("content");
  2214.     for ( var i = 0; i < tabBrowser.browsers.length; i++ ) {
  2215.       var browser = tabBrowser.browsers[i];
  2216.       if (uri.equals(browser.currentURI)) {
  2217.         var resource = new MicrosummaryResource(uri);
  2218.         resource.initFromDocument(browser.contentDocument);
  2219.         return resource;
  2220.       }
  2221.     }
  2222.   }
  2223.  
  2224.   return null;
  2225. }
  2226.  
  2227. /**
  2228.  * Get a value from a pref or a default value if the pref doesn't exist.
  2229.  *
  2230.  * @param   prefName
  2231.  * @param   defaultValue
  2232.  * @returns the pref's value or the default (if it is missing)
  2233.  */
  2234. function getPref(prefName, defaultValue) {
  2235.   try {
  2236.     var prefBranch = Cc["@mozilla.org/preferences-service;1"].
  2237.                      getService(Ci.nsIPrefBranch);
  2238.     switch (prefBranch.getPrefType(prefName)) {
  2239.     case prefBranch.PREF_BOOL:
  2240.       return prefBranch.getBoolPref(prefName);
  2241.     case prefBranch.PREF_INT:
  2242.       return prefBranch.getIntPref(prefName);
  2243.     }
  2244.   }
  2245.   catch (ex) { /* return the default value */ }
  2246.   
  2247.   return defaultValue;
  2248. }
  2249.  
  2250.  
  2251. // From http://lxr.mozilla.org/mozilla/source/browser/components/search/nsSearchService.js
  2252.  
  2253. /**
  2254.  * Removes all characters not in the "chars" string from aName.
  2255.  *
  2256.  * @returns a sanitized name to be used as a filename, or a random name
  2257.  *          if a sanitized name cannot be obtained (if aName contains
  2258.  *          no valid characters).
  2259.  */
  2260. function sanitizeName(aName) {
  2261.   const chars = "-abcdefghijklmnopqrstuvwxyz0123456789";
  2262.   const maxLength = 60;
  2263.  
  2264.   var name = aName.toLowerCase();
  2265.   name = name.replace(/ /g, "-");
  2266.   //name = name.split("").filter(function (el) {
  2267.   //                               return chars.indexOf(el) != -1;
  2268.   //                             }).join("");
  2269.   var filteredName = "";
  2270.   for ( var i = 0 ; i < name.length ; i++ )
  2271.     if (chars.indexOf(name[i]) != -1)
  2272.       filteredName += name[i];
  2273.   name = filteredName;
  2274.  
  2275.   if (!name) {
  2276.     // Our input had no valid characters - use a random name
  2277.     for (var i = 0; i < 8; ++i)
  2278.       name += chars.charAt(Math.round(Math.random() * (chars.length - 1)));
  2279.   }
  2280.  
  2281.   if (name.length > maxLength)
  2282.     name = name.substring(0, maxLength);
  2283.  
  2284.   return name;
  2285. }
  2286.  
  2287.  
  2288.  
  2289.  
  2290.  
  2291. var gModule = {
  2292.   registerSelf: function(componentManager, fileSpec, location, type) {
  2293.     componentManager = componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  2294.     
  2295.     for (var key in this._objects) {
  2296.       var obj = this._objects[key];
  2297.       componentManager.registerFactoryLocation(obj.CID,
  2298.                                                obj.className,
  2299.                                                obj.contractID,
  2300.                                                fileSpec,
  2301.                                                location,
  2302.                                                type);
  2303.     }
  2304.   },
  2305.   
  2306.   unregisterSelf: function(componentManager, fileSpec, location) {},
  2307.  
  2308.   getClassObject: function(componentManager, cid, iid) {
  2309.     if (!iid.equals(Components.interfaces.nsIFactory))
  2310.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  2311.   
  2312.     for (var key in this._objects) {
  2313.       if (cid.equals(this._objects[key].CID))
  2314.       return this._objects[key].factory;
  2315.     }
  2316.     
  2317.     throw Components.results.NS_ERROR_NO_INTERFACE;
  2318.   },
  2319.   
  2320.   _objects: {
  2321.     service: {
  2322.       CID        : Components.ID("{460a9792-b154-4f26-a922-0f653e2c8f91}"),
  2323.       contractID : "@mozilla.org/microsummary/service;1",
  2324.       className  : "Microsummary Service",
  2325.       factory    : MicrosummaryServiceFactory = {
  2326.                      createInstance: function(aOuter, aIID) {
  2327.                        if (aOuter != null)
  2328.                          throw Components.results.NS_ERROR_NO_AGGREGATION;
  2329.                        var svc = new MicrosummaryService();
  2330.                        svc._init();
  2331.                       return svc.QueryInterface(aIID);
  2332.                      }
  2333.                    }
  2334.     }
  2335.   },
  2336.   
  2337.   canUnload: function(componentManager) {
  2338.     return true;
  2339.   }
  2340. };
  2341.  
  2342. function NSGetModule(compMgr, fileSpec) {
  2343.   return gModule;
  2344. }
  2345.